const mqtt = require('mqtt');
const crypto = require('crypto');
const axios = require("axios");
const algorithm = 'aes-128-ecb';
const MessagePack = require('what-the-pack');
const {encode, decode} = MessagePack.initialize(2 ** 22); // 4MB

const port = '/dev/ttyUSB0';
const serialport = require('serialport');

const config = require("./config");

let client = null;
let accessKey;

const deviceId = config.deviceId;
const subDev = new Set();
const secretKey = genSecretKey();

let sn = 0;
const devices = {};
//-----------------util----------------------
Buffer.prototype.toHex8 = function (offset) {
    return ("00" + this.readUInt8(offset).toString(16)).substr(-2).toUpperCase();
};

Buffer.prototype.toHex16LE = function (offset) {
    return ("0000" + this.readUInt16LE(offset).toString(16)).substr(-4).toUpperCase();
};

Buffer.prototype.toHex16BE = function (offset) {
    return ("0000" + this.readUInt16BE(offset).toString(16)).substr(-4).toUpperCase();
};

Buffer.prototype.toHex32LE = function (offset) {
    return ("00000000" + this.readUInt32LE(offset).toString(16)).substr(-8).toUpperCase();
};

Buffer.prototype.toHex32BE = function (offset) {
    return ("00000000" + this.readUInt32BE(offset).toString(16)).substr(-8).toUpperCase();
};

Buffer.prototype.parseAscii = function (start, len) {
    let ret = this.subarray(start, start + len);
    ret = ret.toString('ascii');
    clog(ret);
    ret = ret.substring(0, ret.lastIndexOf("}") + 1);
    ret = JSON.parse(ret);
    return ret;
};

Number.prototype.toHex8 = function () {
    return ("00" + this.toString(16)).substr(-2).toUpperCase();
};

Number.prototype.toHex16 = function () {
    return ("0000" + this.toString(16)).substr(-4).toUpperCase();
};

Number.prototype.toHex32 = function () {
    return ("00000000" + this.toString(16)).substr(-8).toUpperCase();
};

//-----------------util----------------------

function clog(...msg) {
    for (let m of msg) {
        console.log(m);
    }
}

//----------------------------------serialPort-------------------------------------------------------

const headBuf = Buffer.from('55AA', 'hex');

const serialPort = new serialport(port, {
    baudRate: 115200,
    dataBits: 8,
    parity: 'none',
    stopBits: 1,
});


serialPort.on('error', function (err) {
    console.log(err);
});

//处理串口接收数据
function process(data) {
    clog("received:", data);
    let command = data.toHex8(3);
    switch (command) {
        case "0D": {//上报设备状态
            processReportData(data);
            break;
        }
        case "06": {
            clog("设备收到允许入网通知");
            break;
        }
        case "08": {//设备入网消息
            accessNetwork(data);
            break;
        }
        case "09": {//移除设备通知
            clog("移除设备成功");
            break;
        }
        case "0A": {//设备心跳消息
            let len = data.readUInt16BE(4);
            let ret = data.parseAscii(6, len);
            let subId = Buffer.from(ret.sub_id, 'ascii').toHex32BE();
            let dev = devices[subId];
            if (dev) {
                checkDevAttr(dev, subId);
            } else {
                queryDevicesStates();
            }
            break;
        }
        case "1C": {//查询设备列表
            let cmd = ["55", "AA", "00", "1C"];
            let dataLen = (subDev.size * 5 + 2).toHex16();
            cmd.push(dataLen);
            cmd.push("00");
            let subNum = subDev.size.toHex8();
            cmd.push(subNum);
            subDev.forEach(r => cmd.push("04" + r));
            writeToDevice(cmd);
            break;
        }
        case "11": {//查询本地时间
            let cmd = ["55", "AA", "00", "11", "00", "08", "01"];
            let date = new Date();
            let year = (date.getFullYear() - 2000).toHex8();
            cmd.push(year);
            let mouth = (date.getMonth() + 1).toHex8();
            cmd.push(mouth);
            let dayOfMouth = date.getDate().toHex8();
            cmd.push(dayOfMouth);
            let hour = date.getHours().toHex8();
            cmd.push(hour);
            let minutes = date.getMinutes().toHex8();
            cmd.push(minutes);
            let second = date.getSeconds().toHex8();
            cmd.push(second);
            let dayOfWeek = date.getDay();
            if (dayOfWeek === 0) dayOfWeek = 7;
            dayOfWeek = dayOfWeek.toHex8();
            cmd.push(dayOfWeek);
            writeToDevice(cmd);
            break;
        }
        case "13": {//允许关闭入网
            let cmd = ["55", "AA", "00", "07", "00", "00"];
            console.log("发送关闭入网");
            writeToDevice(cmd);
            break;
        }
        case "19": {//同步删除设备
            let cmd = ["55", "AA", "00", "19", "00", "01", "00"];
            console.log("发送同步删除设备");
            let len = data.readUInt16BE(4);
            let ret = data.parseAscii(6, len);
            let subId = Buffer.from(ret.sub_id, 'ascii').toHex32BE();
            subDev.delete(subId);
            delete devices[subId];
            writeToDevice(cmd);
            break;
        }
    }
}


function accessNetwork(buf) {
    clog('收到入网数据：', buf);
    let len = buf.readUInt16BE(4);
    let ret = buf.parseAscii(6, len);
    let subId = Buffer.from(ret.sub_id, 'ascii').toHex32BE();
    let cmd = ["55", "AA", "00", "08", "00", "01", "00"];
    clog("发送入网回应");
    writeToDevice(cmd);

    cmd = ["55", "AA", "00", "13"];
    let retMsg = {"cids": [ret.sub_id], "rets": [0]};
    let retData = Buffer.from(JSON.stringify(retMsg), 'ascii');
    cmd.push(retData.length.toHex16());
    cmd.push(retData.toString('hex'));
    console.log("发送入网成功回应");
    writeToDevice(cmd);
    devices[subId] = {};
    checkDevAttr(devices[subId], subId);
    sendStateToMqtt("1", subId);
}

//处理上报数据
function processReportData(data) {
    checkSnRange();
    let subId = data.toHex32BE(7);
    let dpId = data.toHex8(11);
    let msg = {
        "sn": (sn++).toString(),
        "sender": deviceId,
        "receiver": "",
        "uuid": subId,
        "behavior": 103,
        "body": [{
            "endpoint": 1,
            "svcId": "31",
            "attrId": "",
            "attrValue": "",
        }]
    };
    if (!devices[subId]) {
        devices[subId] = {};
    }
    let dev = devices[subId];
    // 判断设备状态是否发生变化，如果发生变化则上报状态，否则不上报
    let send = true;
    switch (dpId) {
        case "01": {
            let onOff = data.toHex8(15);
            onOff = onOff === "00" ? 0 : 1;
            onOff = onOff.toString();
            msg.body[0].attrId = "1";
            msg.body[0].attrValue = onOff;
            if (dev["1"] === onOff) {
                send = false;
            }
            dev["1"] = onOff;
            break;
        }
        case "02": {
            let temp = data.readUInt32BE(15);
            temp = temp / 10;
            temp = temp.toString();
            msg.body[0].attrId = "2";
            msg.body[0].attrValue = temp;
            if (dev["2"] === temp) {
                send = false;
            }
            dev["2"] = temp;
            break;
        }
        case "03": {
            let temp = data.readUInt32BE(15);
            temp = temp / 10;
            temp = temp.toString();
            msg.body[0].attrId = "3";
            msg.body[0].attrValue = temp;
            if (dev["3"] === temp) {
                send = false;
            }
            dev["3"] = temp;
            break;
        }
        case "04": {
            let mode = data.readUInt8(15);
            mode = mode.toString();
            msg.body[0].attrId = "4";
            msg.body[0].attrValue = mode;
            if (dev["4"] === mode) {
                send = false;
            }
            dev["4"] = mode;
            break;
        }
        default:
            send = false;
    }
    if (send) {
        sendToMqtt(msg);
    }
    checkDevAttr(dev, subId);
}

/**
 * 检查设备属性,判断设备是否为新上线
 * @param dev 子设备
 * @param subId 子设备ID
 */
function checkDevAttr(dev, subId) {
    dev['hb_time'] = Date.now();
    if (!dev['attr'] || dev['attr']['state'] !== '1') {
        dev['attr'] = {};
        fillDevAttr(dev['attr'], subId);
        sendStateToMqtt('1', subId);
    }
    subDev.add(subId);
}

/**
 * 填充设备属性
 * @param devAttr
 * @param subId
 */
function fillDevAttr(devAttr, subId) {
    devAttr['state'] = '1';
    devAttr['owner'] = deviceId;
    devAttr['subDevId'] = subId;
    devAttr['subType'] = 'Rt';
    devAttr['services'] = {"1": "31"};
}

/**
 * 生成校验码
 * @param data
 * @returns {string}
 */
function crc(data) {
    data = Buffer.from(data.join(""), 'hex');
    let sum = 0;
    for (let d of data) {
        sum += d;
    }
    sum -= 256;
    sum = ("00" + sum.toString('16')).substr(-2);
    return sum;
}

function writeToDevice(data) {
    data.push(crc(data));
    data = Buffer.from(data.join(""), 'hex');
    console.log("send data:");
    console.log(data);
    serialPort.write(data);
}

/**
 *  生成密钥
 * @returns {string}
 */
function genSecretKey() {
    const md5 = crypto.createHash('md5');
    let _secretKey = md5.update(config.secretKey).digest('hex');
    _secretKey = _secretKey.substring(_secretKey.length - 16);
    return _secretKey;
}

/**
 *  对发送到好享家的数据进行加密
 * @param data 待加密的数据
 * @returns {string}
 */
function encryption(data) {
    let iv = "";
    let clearEncoding = 'utf8';
    let cipherEncoding = 'base64';
    let cipherChunks = [];
    let cipher = crypto.createCipheriv(algorithm, secretKey, iv);
    cipher.setAutoPadding(true);
    cipherChunks.push(cipher.update(encode(data), clearEncoding, cipherEncoding));
    cipherChunks.push(cipher.final(cipherEncoding));
    return cipherChunks.join('');
}

/**
 * 对从好享家收到的数据进行解密
 * @param data 待解密的数据
 */
function decryption(data) {
    let iv = "";
    let cipherEncoding = 'base64';
    let decipher = crypto.createDecipheriv(algorithm, secretKey, iv);
    decipher.setAutoPadding(true);
    let buf1 = decipher.update(data, cipherEncoding);
    let buf2 = decipher.final();
    return decode(Buffer.concat([buf1, buf2], (buf1.length + buf2.length)));
}

async function getAuth() {
    return axios({
        method: 'post',
        url: 'http://testiot.hosjoy.com:2286/api/device/mqtt/' + config.deviceId,
        headers: {
            "AccessKeyId": "vmrqpyvwxvps"
        },
        data: {
            "secretKey": config.secretKey
        }
    });
}

async function initMqttClient(auth) {
    client = mqtt.connect(auth.protocol + '://' + auth.address, {
        port: auth.port,
        username: auth.mqttUser,
        password: auth.mqttPwd,
        clientId: deviceId,
        will: {
            topic: `hosjoy/zigbee/state/${deviceId}`,
            payload: encryption(getStateMsg("0", "")),
            qos: 1,
            retain: true
        }
    });

    return new Promise((resolve, reject) => {
        client.on('connect', function () {
            console.log('connected.....');
            // client.subscribe(`hosjoy/zigbee/state/${deviceId}`);
            client.subscribe(`hosjoy/zigbee/recv/${deviceId}`);
            setTimeout(function () {
                sendStateToMqtt("1", "");
            }, 2000);
            resolve(client);
        });

    });
}

/**
 * 当网关连上好享家服务器后，启动数据监听
 */
function startListener() {

    //对串口数据进行 粘包、半包处理
    let data = null;
    serialPort.on('data', function (buff) {
        if (data === null) {
            data = buff;
        } else {
            data = Buffer.concat([data, buff], data.length + buff.length);
        }
        if (data.indexOf(headBuf) === 0) {
            data = data.slice(headBuf.length);
        }
        let position = data.indexOf(headBuf);
        if (position > 0) {
            let _data = Buffer.concat([headBuf, data.slice(0, position)], headBuf.length + position);
            process(_data);
            data = data.slice(position);
        }
    });

    client.on('message', function (topic, message) {
        console.log(topic);
        console.log(message.toString());
        let msg = {};
        try {
            msg = decryption(message.toString());
            console.log(msg);
        } catch (e) {
            console.log(e);
            return;
        }
        checkSnRange();
        msg.receiver = msg.sender;
        msg.sender = deviceId;
        let subId = msg.uuid;
        switch (msg.behavior) {
            case 101: {
                msg.behavior = 102;
                for (let o of msg.body) {
                    switch (o.svcId) {
                        case '31': {
                            let val = o.cmdValue;
                            switch (o.cmdId) {
                                case '1': {//设置开关
                                    let cmd = ["55", "AA", "00", "0C", "00", "0A", "04", subId, "01", "01", "00", "01"];
                                    val = parseInt(val);
                                    val = val.toHex8();
                                    cmd.push(val);
                                    writeToDevice(cmd);
                                    break;
                                }
                                case '2': {//设置温度
                                    let cmd = ["55", "AA", "00", "0C", "00", "0D", "04", subId, "02", "02", "00", "04"];
                                    val = parseFloat(val);
                                    val = val * 10;
                                    val = val.toHex32();
                                    cmd.push(val);
                                    writeToDevice(cmd);
                                    break;
                                }
                                case '4': {//设置模式
                                    let cmd = ["55", "AA", "00", "0C", "00", "0A", "04", subId, "04", "04", "00", "01"];
                                    val = parseInt(val);
                                    val = val.toHex8();
                                    cmd.push(val);
                                    writeToDevice(cmd);
                                    break;
                                }
                            }
                            break;
                        }
                        case '100': {
                            switch (o.cmdId) {
                                case '3': {//查询设备列表
                                    queryDevicesStates();
                                    o.content = [];
                                    Object.keys(devices).forEach(r => {
                                        if (devices[r]['attr']) o.content.push(devices[r]['attr']);
                                    });
                                    break;
                                }
                                case '4': {//删除子设备
                                    let cmd = ["55", "AA", "00", "09"];
                                    let subId = o.cmdValue;
                                    subId = Buffer.from(subId, 'hex').toString('ascii');
                                    let data = {"sub_id": subId};
                                    data = Buffer.from(JSON.stringify(data), 'ascii');
                                    cmd.push(data.length.toHex16());
                                    cmd.push(data.toString('hex'));
                                    writeToDevice(cmd);
                                    sendStateToMqtt("0", Buffer.from(subId, 'ascii').toString('hex'));
                                    break;
                                }
                                case '9': {//允许入网
                                    let cmd = ["55", "AA", "00", "06", "00", "00"];
                                    writeToDevice(cmd);
                                    break;
                                }
                            }
                            break;
                        }
                    }
                    // 0 : 成功   1：未知 其它：失败
                    if (!o.status) o.status = 0;
                    if (!o.content) o.content = "";
                    delete o.cmdValue;
                }
                sendToMqtt(msg);
                break;
            }
            case 106: {
                queryDevicesStates();
                msg.behavior = 107;
                for (let o of msg.body) {
                    if (o.svcId === '31') {
                        if (devices[subId] && devices[subId][o.attrId]) {
                            o.attrValue = devices[subId][o.attrId];
                            o.status = 0;
                        } else {
                            o.status = 1;
                        }
                        o.content = "";
                    }
                }
                sendToMqtt(msg);
                break;
            }
        }

    });

}

function checkSnRange() {
    if (sn >= 65535) {
        sn = 0;
    }
}

function sendToMqtt(msg) {
    console.log("sendToMqtt:");
    console.log(msg);
    if (client) client.publish(`hosjoy/zigbee/data/${deviceId}`, encryption(msg));
}

/**
 *
 * @param state 1：上线  0：下线
 * @param subId 子设备ID
 */
function sendStateToMqtt(state, subId) {
    let msg = getStateMsg(state, subId);
    clog("sendStateToMqtt:", msg);
    if (client) client.publish(`hosjoy/zigbee/state/${deviceId}`, encryption(msg));
}

/**
 * 获取设备上下线消息
 * @param state
 * @param subId
 * @returns {{receiver: string, sender, sn: string, behavior: number, body: {state: *, services: {"1": string}, type: string}[], uuid: *}}
 */
function getStateMsg(state, subId) {
    return {
        "sn": "1",
        "sender": deviceId,
        "receiver": "",
        "behavior": 2,
        "uuid": subId,
        "body": [{"state": state, "type": "Rt", "services": {"1": "31"}}]
    };
}

/**
 * 查询设备状态
 */
function queryDevicesStates() {
    let cmd = ["55", "AA", "00", "0B", "00", "00"];
    writeToDevice(cmd);
}

/**
 * 定时查询设备状态
 */
function pollingDevicesStates() {
    setInterval(function () {
        queryDevicesStates();
    }, 60000)
}

/**
 * 报告网关准备就绪
 */
function reportGWReady() {
    let cmd = ["55", "AA", "00", "03", "00", "01", "04"];
    writeToDevice(cmd);
}

/**
 * 定时发送心中数据 40 秒发一次
 */
function heartbeat() {
    setInterval(function () {
        subDev.forEach(r => {
            let cmd = ["55", "AA", "00", "0A"];
            if (r['attr']) {
                let subId = r['attr']['subDevId'];
                subId = Buffer.from(subId, 'hex').toString('ascii');
                let data = {"sub_id": subId};
                data = Buffer.from(JSON.stringify(data), 'ascii');
                cmd.push(data.length.toHex16());
                cmd.push(data.toString('hex'));
                writeToDevice(data);
            }
        });
    }, 40000);

    setInterval(function () {
        let now = Date.now();
        Object.keys(devices).forEach(r => {
            let dev = devices[r];
            if (now - dev.hb_time > 80000) {
                if (dev['attr']) {
                    dev.attr.state = '0';
                    sendStateToMqtt('0', r);
                }
            }
        });
    }, 20000);
}

async function start() {
    let auth = await getAuth();
    auth = auth.data.data;
    accessKey = auth.accessKey;
    clog("获取谁信息:", auth);
    client = await initMqttClient(auth);
    startListener();

    reportGWReady();
    pollingDevicesStates();
    heartbeat();
}

start();


